//pnpm install -g typescript
//pnpm  install @types/node -D
//tsc --init  =>  tsc your.ts  => node your.js


//原始类型(主动赋值)
let age_: number;   //age_ = 1  => success
let age__: string;   //age__ = 1 => error(The value must be a string)
//原始类型(自动推导)
let age = 0; //第一次赋值的时候如果不指定类型,TS为自动检验你的类型
age = 1;             //再一次赋值,如果是别的类型就会报错: age = 'str'  =>  error 


//数组
let userNames1: string[] = [];  //数组成员是string
let userNames2: Array<string> = [];
//Set(无序不重复的集合)
const set = new Set<number>();
set.add(1);
//Map(键值对)
const map = new Map<string, number>();
map.set('long', 18)



//接口
interface User {
  name: string;
  age: number;
  sex?: boolean;
}
const userArr: User[] = [ 
  { name: 'zs', 'age': 1 }, { name: 'lisi', age: 2 }, { name: 'wangwu', age: 3, sex: true }
]

interface Test{
  name : string,
  age : number,
  sex : boolean,
  arr:{
    [key: string]: string | number;  // interface Member{ [key: string]: string | number }
  }
}



/*枚举
1.相比对象类型,还可以看到值  
2.会自动+1,即前一个枚举值+1(auto=3) 
3.支持函数计算  
4.枚举值必须是一个编译时常量(编译时就必须确定值,比如不能用运行时才生效的函数toFiexed)   除非使用常量枚举
*/
const randomFn = () => (Math.random() * 10);
enum Status { success = 1, fail = 2, auto, unknow = randomFn() }
console.log(Status.auto, Status.unknow);



//Class类:  实例化:继承类内部的属性和方法  （面向对象: 字面意思,面向的是一个对象,对象就会有自己的属性方法,更好维护管理继承关系和复用）
class Person {
  private name: string;
  private age: number;

  constructor(personName: string, personAge: number) {
    this.name = personName;
    this.age = personAge;
  }

  getDesc(): string {
    return `${this.name} at ${this.age} years old`;
  }
}
//ps: 工具类可以这么封装,比较好维护 
export class DateUtils {
  static isSameDate() { }   //static: 不用实例化即可调用,
  static diffDate() { }
}
export class NumberUtils { /*...*/ }
export class UserListUtils { /*...*/ }



//unknow | never
interface IUser { job?: IJob; }
interface IJob { title: string; }

const user: IUser = { job: { title: 'bar' } };

const { job = {} } = user;
//第一次解构赋值时，为 job 提供了一个空对象作为默认值，TypeScript 会认为此时 job 的类型就是一个空对象，
//const { title } = job;  类型“{}”上不存在属性“title”。 
const { job: jobs = {} as IJob } = user;
const { title } = jobs;




//类型别名|类型自定义|类型变量   可定义函数、原始类型、引用类型、字面量、联合类型( | & )、接口类型

//1.函数
type Sum = (a: number, b: number) => number;
const dosum: Sum = (a, b) => {
  return a + b;
}
//2.字面量
type dbname = 'newking';
let dbname: dbname = 'newking';  //只能是newking

//3.联合(字面量)
type rolename = 'zhangsan' | 'lisi' | 'wangwu' | dbname;
let rolename: rolename = 'newking';     //可以是其中之一

//4.联合(合并接口)
type joininterface = IUser & { name: string }

//5.交集
type UnionIntersection = (1 | 2 | 3) & (1 | 2); // 1 | 2



//type工具类型
type User_ = { name: string; age: number, email: string };
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<PartialUser>;

  //Partial => 可以不实现全部的属性了↓
  const partialUser: PartialUser = {
    name: 'John Doe', //skip: age email 
  };


  //Readonly => 只读
  const readonlyUser: ReadonlyUser = {
    name: 'newking'  //这里的name为只读
  }


  //Pick => 只提取 name、age 两个类型   ps:Omit和Pick相反
  type PickUser = Pick<User_, 'name' | 'age'>;
  const pickUser: PickUser = {
    name: 'John Doe',
    age: 30,
  };


  //Record<K,T> => 构造一个对象类型，并将K中的所有属性值都转换为T类型
  type state = "created" | "submitted" | "removed"
  export const states:Record<state,string> = {  //:Record<state | "more" ,string> 
    created:'01',
    submitted:'02',
    removed:'03'
  }

  //Record 类型来声明属性名还未确定的接口类型
  type MoreRecord = Record<string, string>;

  const moreRecord: MoreRecord = {
    name: 'John Doe',
    job: 'fe-developer',
    email: 'john.doe@example.com',
    bio: 'Make more interesting things!',
    type: 'vip',
    //...
  };


  //Exclue 差集
  type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address';
  type RequiredUserProps = 'name' | 'email';
  // OptionalUserProps 的类型为 'age' | 'phone' | 'address'
  type OptionalUserProps = Exclude<UserProps, RequiredUserProps>; 







type User_noRequired = { name?: string, age?: number, email?: string }
type RequiredUser = Required<User_noRequired>;

  //Required => 必须实现全部属性了
  const requiredUser: RequiredUser = {
    name: 'newking',
    age: 30,
    email: 'newking@new.com'
  }







//泛型(类型的参数)

//传参
type Status_<T> = 'success' | 'fail' | T;
type NewStatus = Status_<number>;  //number

let status2: Status_<string> = 'success_';  //相当于 type Status_ = 'success' | 'fail' | string
let status1: NewStatus = 666;               //相当于 type Status_ = 'success' | 'fail' | number

/*自动推导
  
  前言: 有一个这样的函数: function factory(input: string ):string
       它的出参与入参类型是完全一致的,如果需要修改类型,或者新增类型呢? 
       你可以用联合类型input: string | number
       但不够灵活,总而言之都必须修改函数
 
       而使用泛型,即可完美解决这个问题 ↓↓↓
*/

//场景1    
function factory<T>(input: T): T {
  return input
}
factory<string>('new-king')        // 返回string
factory(true)                      // 自动推导 => 返回boolean


//场景2(计算长度)
/*由于T是什么类型还不确定,直接访问长度是不允许的,因为有些类型没有length属性
    function getLength<T>(input:T): number{
      return input.length
    } 
    所以要用 extends 让 T 继承拥有length属性的对象
    如: function getLength<T extends {length:number}>(input:T)
    或者↓↓↓
*/


//可传字符串和数组
function getLength<T extends string | any[]>(input: T): number {
  return input.length;
}
getLength('123');
getLength(['1', 2, true]);


//可指定传任何类型数组
function getLength_<T>(input: T[]): number {
  return input.length;
}

getLength_<string>(['hello', 'world']);  //只能传 string[]
getLength_(['str', true])   // 可传string[] | boolean[] (自动推导)





/* 类型声明 *.d.ts 文件
 

  declare module "lodash" {
    camelCase(string?: string): string;
    capitalize(string?: string): string;
    endsWith(string?: string): string;
    ...
  }

  使用declare 关键字 为模块（module）lodash 进行类型声明（declare）
  在导入这个模块并访问属性时，你需要提示这个对象上具有 camelCase，capitalize 等方法。
  类型声明文件是仅仅包含类型信息，并且无需导入操作，就能够被 TypeScript 自动加载的文件。

  
  lib.xxx.d.ts 文件称为内置类型声明文件
  window.document
  window.location
   
   
 */


 





















/*疑问
1. any、never、unknow的作用和使用场景

unkonw: 类似any,但也有类型限制

*/


//其他
function a(): undefined {  // ts5.1版本以下 undefined在ts中也属于void,如果确定要返回undefined,必须显式return
  return;
}



